/* kXML
 *
 * The contents of this file are subject to the Enhydra Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License
 * on the Enhydra web site ( http://www.enhydra.org/ ).
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific terms governing rights and limitations
 * under the License.
 *
 * The Initial Developer of kXML is Stefan Haustein. Copyright (C)
 * 2000, 2001 Stefan Haustein, D-46045 Oberhausen (Rhld.),
 * Germany. All Rights Reserved.
 *
 * Contributor(s): Paul Palaszewski, Wilhelm Fitzpatrick, 
 *                 Eric Foster-Johnson, Hans-Harald Schulz
 *
 * */

package org.kxml.io;

import java.io.IOException;
import java.io.Writer;

import org.kxml.PrefixMap;
import org.kxml.Xml;

/** a concrete XML Writer */

public class XmlWriter extends AbstractXmlWriter {

    static char[] indent = { '\r', '\n', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
            ' ', ' ', ' ', ' ', ' ', ' ', ' ' };
    protected Writer writer;
    boolean pending = false;
    int indentLevel = 0;

    int noIndent = Integer.MAX_VALUE;

    /** creates a new xmlWritet based on the given writer */

    public XmlWriter(Writer writer) {
        this.writer = writer;
    }

    /** writes a degenerated tag with the given name and attributes */

    public void attribute(String name, String value) throws IOException {

        if (!pending)
            throw new RuntimeException(
                    "can write attr only immediately after a startTag");

        writer.write(' ');
        writer.write(name);
        writer.write("=\"");
        writer.write(Xml.encode(value, Xml.ENCODE_QUOT));
        writer.write('"');

        if (name.equals("xml:space") && value.equals("preserve"))
            noIndent = indentLevel;
    }

    protected void checkPending() throws IOException {
        if (pending) {
            writer.write('>');
            pending = false;
        }
    }

    /** closes the XmlWriter by closing the underlying writer */

    public void close() throws IOException {
        flush();
        writer.close();
    }

    /** writes an end tag. */

    public void endTag() throws IOException {
        indentLevel--;

        if (pending) {
            writer.write(" />");
            pending = false;
        } else {
            if (indentLevel + 1 < noIndent)
                writeIndent();

            writer.write("</");
            writer.write(current.tag);
            writer.write(">");
        }

        if (indentLevel + 1 == noIndent)
            noIndent = Integer.MAX_VALUE;

        current = current.prev;
        if (current == null)
            throw new RuntimeException("too many closing tags!");
    }

    /**
     * flushes the XmlWriter. Attention: If a closing angle braket is pending,
     * it will be appended before flushing the underlying writer. Thus, after
     * flush attributes cannot be added any longer
     */

    public void flush() throws IOException {
        checkPending();
        writer.flush();
    }

    /** writes a start tag with the given name */

    protected void startTag(PrefixMap prefixMap, String tag) throws IOException {

        current = new State(current, prefixMap, tag);

        checkPending();

        if (indentLevel < noIndent)
            writeIndent();

        indentLevel++;

        writer.write('<');
        writer.write(tag);

        pending = true;
    }

    /** writes a single character using the xml escaping rules */

    public void write(char c) throws IOException {
        checkPending();
        if (noIndent > indentLevel)
            noIndent = indentLevel;

        switch (c) {
        case '<':
            writer.write("&lt;");
            break;
        case '>':
            writer.write("&gt;");
            break;
        case '&':
            writer.write("&amp;");
            break;
        default:
            writer.write(c);
        }
    }

    /** writes an character array using the XML escaping rules */

    public void write(char[] buf, int start, int len) throws IOException {
        checkPending();
        if (noIndent > indentLevel)
            noIndent = indentLevel;

        int end = start + len;

        do {
            int i = start;

            while (i < end && "<>&".indexOf(buf[i]) == -1)
                i++;

            writer.write(buf, start, i - start);

            if (i == end)
                break;

            write(buf[i]);

            start = i + 1;
        } while (start < end);
    }

    public void writeIndent() throws IOException {

        int l = indentLevel + 2;

        if (l < 2)
            l = 2;
        else if (l > indent.length)
            l = indent.length;

        checkPending();
        // writer.write ("<!-- i -->");
        writer.write(indent, 0, l);
    }

    /**
     * ATTENTION: Application needs to take care about not writing illegal
     * character sequences (like comment end in comments)
     */

    public void writeLegacy(int type, String content) throws IOException {
        checkPending();
        switch (type) {
        case Xml.COMMENT:
            writer.write("<!--");
            writer.write(content);
            writer.write("-->");

            break;
        case Xml.DOCTYPE:
            writer.write("<!DOCTYPE ");
            writer.write(content);
            writer.write(">");
            break;

        case Xml.PROCESSING_INSTRUCTION:
            writer.write("<?");
            writer.write(content);
            writer.write("?>");
            break;
        }
    }

    /**
     * writes a string without escaping. Use with care! Not available in wbxml
     * writer
     */

    public void writeRaw(String s) throws IOException {
        checkPending();
        writer.write(s);
    }
}
